/*
 * Example using libnice to negotiate a UDP connection between two clients,
 * possibly on the same network or behind different NATs and/or stateful
 * firewalls.
 *
 *
 * Run two clients, one controlling and one controlled:
 *   simple-example 0 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }')
 *   simple-example 1 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }')
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include <nice/agent.h>

#if GLIB_CHECK_VERSION(2, 36, 0)
#include <gio/gnetworking.h>
#endif

#include "ice_client.h"
#include "custom.h"


static GMainLoop *gloop;
static GIOChannel* io_stdin;
static guint stream_id;

static const gchar *candidate_type_name[] = {"host", "srflx", "prflx", "relay"};

static const gchar *state_name[] = {"disconnected", "gathering", "connecting", "connected", "ready", "failed"};

static int print_local_data(NiceAgent *agent, guint stream_id, guint component_id);
static int parse_remote_data(NiceAgent *agent, guint stream_id, guint component_id, char *line);
static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id, gpointer data);
static void cb_new_selected_pair(NiceAgent *agent, guint stream_id, guint component_id, gchar *lfoundation, gchar *rfoundation, gpointer data);
static void cb_component_state_changed(NiceAgent *agent, guint stream_id, guint component_id, guint state, gpointer data);
static void cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id, guint len, gchar *buf, gpointer data);
static gboolean remote_info_cb (GIOChannel *source, GIOCondition cond, gpointer data);
static gboolean stdin_send_data_cb (GIOChannel *source, GIOCondition cond, gpointer data);
static gboolean set_remote_info(NiceAgent *agent, char *peer_ice);

static char* g_ice_candidate = NULL;
static int g_ice_collect_done = 0;

static int g_ice_pipe[2];

static void
cb_candidate_gathering_done(NiceAgent *agent, guint _stream_id, gpointer data)
{
	// Candidate gathering is done. Send our local candidates on stdout
	printf("Copy this line to remote client:\n");
	printf("\n  ");
	print_local_data(agent, _stream_id, 1);
	printf("\n");

	ice_config *cfg = (ice_config *)data;

	printf("cb_candidate_gathering_done, peer_ice: %s\n", cfg->peer_ice);

	if (NULL == cfg->peer_ice)
	{
		ReqConn(cfg->peer_id, g_ice_candidate);

		// Listen on pipe for the remote candidate list
		g_io_add_watch(g_io_channel_unix_new(g_ice_pipe[0]), G_IO_IN, remote_info_cb, agent);
	}
	else
	{
		RespConn(cfg->peer_id, g_ice_candidate);
		set_remote_info(agent, cfg->peer_ice);
	}
}

static gboolean set_remote_info(NiceAgent *agent, char *peer_ice)
{
	int rval;
	gboolean ret = TRUE;

	printf("set_remote_info, peer_ice: %s\n", peer_ice);

	// Parse remote candidate list and set it on the agent
	rval = parse_remote_data(agent, stream_id, 1, peer_ice);
	if (rval == EXIT_SUCCESS) 
	{
		// Return FALSE so we stop listening to stdin since we parsed the
		// candidates correctly
		ret = FALSE;
	} 
	else 
	{
		fprintf(stderr, "---ERROR: failed to parse remote data\n");
		fflush (stdout);
	}

	return ret;
}

static gboolean remote_info_cb(GIOChannel *source, GIOCondition cond, gpointer data)
{
	NiceAgent *agent = data;
	gchar *line = NULL;
	int rval;
	gboolean ret = TRUE;

	printf("remote_info_cb----------------\n");
	
///*
	int n;
	char buf[1024] = {0};
	if ((n = read(g_ice_pipe[0], buf, sizeof(buf))) > 0)
	{
		printf("remote_info_cb, read from pipe, bytes is %d, buf: %s\n", n, buf);

		//printf("remote_info_cb, line:  %s\n", line);
		
		// Parse remote candidate list and set it on the agent
		rval = parse_remote_data(agent, stream_id, 1, buf);
		if (rval == EXIT_SUCCESS) 
		{
			// Return FALSE so we stop listening to stdin since we parsed the
			// candidates correctly
			ret = FALSE;
			
			g_debug("waiting for state READY or FAILED signal...");
		} 
		else 
		{
			fprintf(stderr, "ERROR: failed to parse remote data\n");
			printf("Enter remote data (single line, no wrapping):\n");
			printf("> ");
			fflush (stdout);
		}
		
		return TRUE;
	}	
//*/

//////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
	//int r = g_io_channel_read_line(source, &line, NULL, NULL, NULL);

	gchar *buff = g_new0(gchar, 1024);
    gsize bytes_read;

	int r = g_io_channel_read_chars(source, buff, 1024, &bytes_read, NULL);
	
	if (r == G_IO_STATUS_NORMAL)
	{
		printf("remote_info_cb, line:  %s\n", line);
		
		// Parse remote candidate list and set it on the agent
		rval = parse_remote_data(agent, stream_id, 1, line);
		if (rval == EXIT_SUCCESS) 
		{
			// Return FALSE so we stop listening to stdin since we parsed the
			// candidates correctly
			ret = FALSE;
			
			g_debug("waiting for state READY or FAILED signal...");
		} 
		else 
		{
			fprintf(stderr, "ERROR: failed to parse remote data\n");
			printf("Enter remote data (single line, no wrapping):\n");
			printf("> ");
			fflush (stdout);
		}
		
		g_free (line);
	}
	else
	{
		printf("remote_info_cb read error!!!!!(%d)\n", r);
	}

	return ret;
}

static void
cb_component_state_changed(NiceAgent *agent, guint _stream_id,
    guint component_id, guint state,
    gpointer data)
{

	g_debug("SIGNAL: state changed %d %d %s[%d]\n", _stream_id, component_id, state_name[state], state);

	if (state == NICE_COMPONENT_STATE_READY) 
	{
		NiceCandidate *local, *remote;

		// Get current selected candidate pair and print IP address used
		if (nice_agent_get_selected_pair (agent, _stream_id, component_id,
					&local, &remote)) 
		{
			gchar ipaddr[INET6_ADDRSTRLEN];
			
			nice_address_to_string(&local->addr, ipaddr);
			
			printf("\nNegotiation complete: ([%s]:%d,", ipaddr, nice_address_get_port(&local->addr));
			
			nice_address_to_string(&remote->addr, ipaddr);
			
			printf(" [%s]:%d)\n", ipaddr, nice_address_get_port(&remote->addr));
		}

		//evan TBD
		///*
		// Listen to stdin and send data written to it
		printf("\nSend lines to remote (Ctrl-D to quit):\n");
		g_io_add_watch(io_stdin, G_IO_IN, stdin_send_data_cb, agent);
		printf("> ");
		fflush (stdout);
		//*/	
	} 
	else if (state == NICE_COMPONENT_STATE_FAILED) 
	{
		g_main_loop_quit (gloop);
	}
}

static gboolean stdin_send_data_cb (GIOChannel *source, GIOCondition cond, gpointer data)
{
	NiceAgent *agent = data;
	gchar *line = NULL;

	if (g_io_channel_read_line (source, &line, NULL, NULL, NULL) == G_IO_STATUS_NORMAL)
	{
		nice_agent_send(agent, stream_id, 1, strlen(line), line);
		g_free (line);
		printf("> ");
		fflush (stdout);
	} 
	else 
	{
		nice_agent_send(agent, stream_id, 1, 1, "\0");
		// Ctrl-D was pressed.
		g_main_loop_quit (gloop);
	}

	return TRUE;
}

static void
cb_new_selected_pair(NiceAgent *agent, guint _stream_id,
    guint component_id, gchar *lfoundation,
    gchar *rfoundation, gpointer data)
{
	g_debug("SIGNAL: selected pair %s %s", lfoundation, rfoundation);
}

static void
cb_nice_recv(NiceAgent *agent, guint _stream_id, guint component_id, guint len, gchar *buf, gpointer data)
{
	if (len == 1 && buf[0] == '\0')
		g_main_loop_quit (gloop);
	
	printf("%.*s", len, buf);
	
	fflush(stdout);
}

static NiceCandidate *
parse_candidate(char *scand, guint _stream_id)
{
  NiceCandidate *cand = NULL;
  NiceCandidateType ntype;
  gchar **tokens = NULL;
  guint i;

  tokens = g_strsplit (scand, ",", 5);
  for (i = 0; tokens[i]; i++);
  if (i != 5)
    goto end;

  for (i = 0; i < G_N_ELEMENTS (candidate_type_name); i++) {
    if (strcmp(tokens[4], candidate_type_name[i]) == 0) {
      ntype = i;
      break;
    }
  }
  if (i == G_N_ELEMENTS (candidate_type_name))
    goto end;

  cand = nice_candidate_new(ntype);
  cand->component_id = 1;
  cand->stream_id = _stream_id;
  cand->transport = NICE_CANDIDATE_TRANSPORT_UDP;
  strncpy(cand->foundation, tokens[0], NICE_CANDIDATE_MAX_FOUNDATION);
  cand->foundation[NICE_CANDIDATE_MAX_FOUNDATION - 1] = 0;
  cand->priority = atoi (tokens[1]);

  if (!nice_address_set_from_string(&cand->addr, tokens[2])) {
    g_message("failed to parse addr: %s", tokens[2]);
    nice_candidate_free(cand);
    cand = NULL;
    goto end;
  }

  nice_address_set_port(&cand->addr, atoi (tokens[3]));

 end:
  g_strfreev(tokens);

  return cand;
}


static int
print_local_data(NiceAgent *agent, guint _stream_id, guint component_id)
{
	int result = EXIT_FAILURE;
	gchar *local_ufrag = NULL;
	gchar *local_password = NULL;
	gchar ipaddr[INET6_ADDRSTRLEN];
	GSList *cands = NULL, *item;

	char *dst = (char *)malloc(256); 
	memset(dst, 0 , 256);
	int total_len = 256;
	int current_len = 0;
	int valid_index = 0;
	int cnt = 0;

	if (!nice_agent_get_local_credentials(agent, _stream_id, &local_ufrag, &local_password))
		goto end;

	cands = nice_agent_get_local_candidates(agent, _stream_id, component_id);
	if (cands == NULL)
		goto end;

	printf("%s %s", local_ufrag, local_password);
	
	sprintf(dst, "%s %s", local_ufrag, local_password);

	current_len = strlen(dst);
	
	//printf("\n==========================================================================\n");

	for (item = cands; item; item = item->next)
	{
		NiceCandidate *c = (NiceCandidate *)item->data;

		nice_address_to_string(&c->addr, ipaddr);

		// (foundation),(prio),(addr),(port),(type)
//Evan		
#if 0		
		printf(" %s,%u,%s,%u,%s",
				c->foundation,
				c->priority,
				ipaddr,
				nice_address_get_port(&c->addr),
				candidate_type_name[c->type]);
#else
		if (!strstr(ipaddr, ":"))
		{
			printf(" %d,%u,%s,%u,%s",
				cnt++,
				0,
				ipaddr,
				nice_address_get_port(&c->addr),
				candidate_type_name[c->type]);
		}
#endif

		//Evan add
		//printf("\n");

		if (!strstr(ipaddr, ":"))
		{
			char tmp[128] = {0};
			sprintf(tmp, " %d,%u,%s,%u,%s",
					valid_index++,
					0,//c->priority,
					ipaddr,
					nice_address_get_port(&c->addr),
					candidate_type_name[c->type]);

			if (current_len + strlen(tmp) > total_len)
			{
				dst = (char *)realloc(dst, total_len*2);
				total_len*=2;
				
				//printf("realloc-------------------------\n");
			}
			
			strcat(dst, tmp);
			current_len += strlen(tmp);
		}
	}
	
	printf("\n==========================================================================\n");
	
	result = EXIT_SUCCESS;

	printf("result: %s(%d, %d)\n", dst, total_len, current_len);

	g_ice_candidate = (char *)malloc(current_len);
	strcpy(g_ice_candidate, dst);

	g_ice_collect_done = 1;

	free(dst);

end:
	if (local_ufrag)
		g_free(local_ufrag);
	
	if (local_password)
		g_free(local_password);
	
	if (cands)
		g_slist_free_full(cands, (GDestroyNotify)&nice_candidate_free);

	return result;
}

static int
parse_remote_data(NiceAgent *agent, guint _stream_id, guint component_id, char *line)
{
	GSList *remote_candidates = NULL;
	gchar **line_argv = NULL;
	const gchar *ufrag = NULL;
	const gchar *passwd = NULL;
	int result = EXIT_FAILURE;
	int i;

	line_argv = g_strsplit_set (line, " \t\n", 0);
	for (i = 0; line_argv && line_argv[i]; i++) 
	{
		if (strlen (line_argv[i]) == 0)
			continue;

		// first two args are remote ufrag and password
		if (!ufrag) 
		{
			ufrag = line_argv[i];
		} 
		else if (!passwd) 
		{
			passwd = line_argv[i];
		} 
		else 
		{
			// Remaining args are serialized canidates (at least one is required)
			NiceCandidate *c = parse_candidate(line_argv[i], _stream_id);

			if (c == NULL) 
			{
				printf("failed to parse candidate: %s\n", line_argv[i]);
				goto end;
			}
			
			remote_candidates = g_slist_prepend(remote_candidates, c);
		}
	}
	
	if (ufrag == NULL || passwd == NULL || remote_candidates == NULL) 
	{
		printf("line must have at least ufrag, password, and one candidate\n");
		goto end;
	}

	if (!nice_agent_set_remote_credentials(agent, _stream_id, ufrag, passwd)) 
	{
		printf("failed to set remote credentials\n");
		goto end;
	}

	// Note: this will trigger the start of negotiation.
	if (nice_agent_set_remote_candidates(agent, _stream_id, component_id, remote_candidates) < 1) 
	{
		printf("failed to set remote candidates\n");
		goto end;
	}

	result = EXIT_SUCCESS;

end:
	if (line_argv != NULL)
		g_strfreev(line_argv);
	
	if (remote_candidates != NULL)
		g_slist_free_full(remote_candidates, (GDestroyNotify)&nice_candidate_free);

	return result;
}

static void *ICELoop(void *arg)
{
	ice_config *cfg = (ice_config *)arg;

	NiceAgent *agent;
	gchar *stun_addr = cfg->ip;
	guint stun_port = cfg->port;
	gboolean controlling = cfg->action;
	
	if (controlling != 0 && controlling != 1)
	{
		fprintf(stderr, "controlling must be 0 or 1\n");
		return NULL;
	}

	//port = 3478;

	g_debug("Using stun server '[%s]:%u'\n", stun_addr, stun_port);

	if(pipe(g_ice_pipe) < 0)
	{
		perror("iceLoop, pipe");
		exit(-1);
	}

#if GLIB_CHECK_VERSION(2, 36, 0)
	g_networking_init();
#else
	g_type_init();
#endif

	gloop = g_main_loop_new(NULL, FALSE);

#ifdef G_OS_WIN32
	io_stdin = g_io_channel_win32_new_fd(_fileno(stdin));
#else
	io_stdin = g_io_channel_unix_new(fileno(stdin));
#endif

	//g_io_channel_set_flags(io_stdin, G_IO_FLAG_NONBLOCK, NULL);

	// Create the nice agent
	agent = nice_agent_new(g_main_loop_get_context (gloop), NICE_COMPATIBILITY_RFC5245);

	if (agent == NULL)
		g_error("Failed to create agent");

	// Set the STUN settings and controlling mode
	if (stun_addr) 
	{
		g_object_set(agent, "stun-server", stun_addr, NULL);
		g_object_set(agent, "stun-server-port", stun_port, NULL);
	}
	
	g_object_set(agent, "controlling-mode", controlling, NULL);

	// Connect to the signals
	g_signal_connect(agent, "candidate-gathering-done", G_CALLBACK(cb_candidate_gathering_done), cfg);
	
	g_signal_connect(agent, "new-selected-pair", G_CALLBACK(cb_new_selected_pair), NULL);
	
	g_signal_connect(agent, "component-state-changed", G_CALLBACK(cb_component_state_changed), NULL);

	// Create a new stream with one component
	stream_id = nice_agent_add_stream(agent, 1);
	if (stream_id == 0)
		g_error("Failed to add stream");

	// Attach to the component to receive the data
	// Without this call, candidates cannot be gathered
	nice_agent_attach_recv(agent, stream_id, 1, g_main_loop_get_context (gloop), cb_nice_recv, NULL);

	// Start gathering local candidates
	if (!nice_agent_gather_candidates(agent, stream_id))
		g_error("Failed to start candidate gathering");

	g_debug("waiting for candidate-gathering-done signal...");

	// Run the mainloop. Everything else will happen asynchronously
	// when the candidates are done gathering.
	g_main_loop_run (gloop);

	g_main_loop_unref(gloop);
	g_object_unref(agent);
	g_io_channel_unref (io_stdin);

	return NULL;
}

void StartICE(ice_config *cfg)
{
	pthread_t pid;

	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

	int ret = pthread_create(&pid, &attr, (void *)ICELoop, (void *)cfg);
	if(ret!=0)
	{
		printf ("Create pthread error!\n");
		exit (1);
	}
}

char *GetICECandidate()
{
	if (1 == g_ice_collect_done)
	{
		return g_ice_candidate;
	}

	return NULL;
}

int GetICEWritePipe()
{
	return g_ice_pipe[1];
}

